/**
 * @file
 * Aspell library interface implementation.
 *
 * Kisa provides spell checking as you type and displays the result in a small
 * window on your Desktop.
 *
 * Copyright (c) 2009 by Pete Black <theblackpeter@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * @author Pete Black <theblackpeter@gmail.com>
 */
#include <QLocale>
#include <QtDebug>

#include "aspelllib.h"

/**
 * Instances of this class will have this name.
 * @note this is not a unique identifier, just a common name.
 */
#ifndef OBJECT_NAME
#define OBJECT_NAME "aspelllib"
#endif

/**
 * Name used when outputting debug information.
 */
#ifndef OBJECT_DEBUG_NAME
#define OBJECT_DEBUG_NAME "aspelllib:"
#endif

AspellLib::AspellLib() {
  initMembers();
  qDebug() << OBJECT_DEBUG_NAME << OBJECT_NAME << "created";
}

AspellLib::~AspellLib() {
  delete_aspell_speller(m_aspellSpeller);

  qDebug() << OBJECT_DEBUG_NAME << OBJECT_NAME << "destroyed";
}

void AspellLib::addToPersonalWordList(const QString& word) {
  const char* aspell_word = word.toLocal8Bit();

  if(aspell_speller_add_to_personal(m_aspellSpeller, aspell_word, qstrlen(aspell_word)))
    qDebug() << OBJECT_DEBUG_NAME << "added" << word << "to personal word list";
  else
    qDebug() << OBJECT_DEBUG_NAME << "could not add" << word << "to personal word list";

  if(aspell_speller_save_all_word_lists(m_aspellSpeller))
    qDebug() << OBJECT_DEBUG_NAME << "updated to personal word list";
  else
    qDebug() << OBJECT_DEBUG_NAME << "could not write to personal word list";
}

void AspellLib::addToSessionWordList(const QString& word) {
  const char* aspell_word = word.toLocal8Bit();

  if(aspell_speller_add_to_session(m_aspellSpeller, aspell_word, qstrlen(aspell_word)))
    qDebug() << OBJECT_DEBUG_NAME << "added" << word << "to session word list (word will be ignored)";
  else
    qDebug() << OBJECT_DEBUG_NAME << "could not add" << word << "to session word list";
}

QStringList AspellLib::getSuggestions(const QString& word) {
  const char* aspell_word = word.toLocal8Bit();

  const AspellWordList* aspell_suggestions =
      aspell_speller_suggest(m_aspellSpeller, aspell_word, -1);

  AspellStringEnumeration* aspell_elements =
      aspell_word_list_elements(aspell_suggestions);

  const char* aspell_suggestion =
      aspell_string_enumeration_next(aspell_elements);

  QStringList suggestions;
  while(aspell_suggestion != NULL) {
    QString suggestion = QString::fromUtf8(aspell_suggestion);
    suggestions << suggestion;
    aspell_suggestion = aspell_string_enumeration_next(aspell_elements);
  }
  qDebug() << OBJECT_DEBUG_NAME << "suggestions:" << suggestions;

  delete_aspell_string_enumeration(aspell_elements);

  return suggestions;
}

void AspellLib::initialize(QString dictionary, const QString& dictDir,
                           const QString& aspellPrefix,
                           const QString& extraDicts) {

  // init aspell with system default
  AspellConfig* aspell_config = new_aspell_config();
  aspell_config_replace(aspell_config, "lang", dictionary.toLocal8Bit());
  aspell_config_replace(aspell_config, "encoding", "utf-8");

  // add user overrides
  if(!dictDir.isEmpty())
    aspell_config_replace(aspell_config, "dict-dir", dictDir.toLocal8Bit());

  if(!aspellPrefix.isEmpty())
    aspell_config_replace(aspell_config, "prefix", aspellPrefix.toLocal8Bit());

  if(!extraDicts.isEmpty())
    aspell_config_replace(aspell_config, "extra-dicts", extraDicts.toLocal8Bit());

  // check for errors, if none create the speller
  AspellCanHaveError* aspell_possible_err = new_aspell_speller(aspell_config);
  if (aspell_error_number(aspell_possible_err) != 0)
    printErrorAndExit(aspell_error_message(aspell_possible_err));

  else
    m_aspellSpeller = to_aspell_speller(aspell_possible_err);

  // we need to iterate over all the dictionaries aspell knows about
  AspellDictInfoList* dictInfoList = get_aspell_dict_info_list(aspell_config);
  AspellDictInfoEnumeration* dictInfoEnum =
      aspell_dict_info_list_elements(dictInfoList);

  // make sure we actual found dictionaries
  if(aspell_dict_info_list_empty(dictInfoList))
    qFatal("kisalib: No dictionaries found");

  const AspellDictInfo* entry;
  while((entry = aspell_dict_info_enumeration_next(dictInfoEnum)) != 0) {

    // available entry struct variables are
    //    entry->name
    //    entry->code
    //    entry->jargon
    //    entry->size_str
    //    entry->module->name;

    // creat a full language_COUNTRY code based on either the language only or
    // on the full 5 characters in the code variable of the dictionary
    // ISO 639/ISO 3166 standard is on the form
    // language[_COUNTRY][.codeset][@modifier]
    QString code = entry->code;
    QLocale locale(code);

    // build up a format similar to that of KSpell, that is something more
    // intuitive for the user
    QString dictionary;
    // in case we only have the language of the dictionary
    if(code.size() == 2) {
      dictionary = QLocale::languageToString(locale.language());
      dictionary += " (common";
    }

    // both language and country coded in 5 characters (language_COUNTRY)
    else {
      // just in case there is other crap there...
      code = code.left(5);

      dictionary = QLocale::languageToString(locale.language());
      dictionary += " (";
      dictionary += QLocale::countryToString(locale.country());
    }

    // every dictionary dosn't have a jargon
    QString jargon = entry->jargon;
    if(!jargon.isEmpty())
      dictionary += " - ";

    dictionary += jargon;
    dictionary += ")";

    // add the user readable dictionary string to the list
    m_dictionaries << dictionary;

    // add the ISO standard to the list, will need it later too...
    m_dictionaryCodes << code;

    // QLocale tries to guess the country based on the language
    m_dictionaryCodesGuessed << locale.name();
  }

  delete_aspell_dict_info_enumeration(dictInfoEnum);
  delete_aspell_config(aspell_config);

  qDebug() << OBJECT_DEBUG_NAME << "available dictionaries" << m_dictionaries;

  updateDictionary(dictionary);
}

void AspellLib::initMembers() {
  // init base class members
  m_currentDictionaryIndex = 0;
  m_dictionaries = QStringList();
  m_dictionaryCodes = QStringList();
  m_dictionaryCodesGuessed = QStringList();
}

bool AspellLib::isMisspelled(const QString& word) {
  const char* aspell_word = word.toLocal8Bit();

  // aspell_speller_check returns 0 if it is not in the dictionary, 1 if it is,
  // or -1 on error
  int spellerResult = aspell_speller_check(m_aspellSpeller, aspell_word, qstrlen(aspell_word));

  if(!spellerResult) {
    qDebug() << OBJECT_DEBUG_NAME << "can't find" << word;
    return true;
  }
  else if(spellerResult == -1)
    qWarning() << OBJECT_NAME << "error accrued when looking up" << word;

  return false;
}

void AspellLib::printErrorAndExit(QString aspellError) {
  // Aspell uses punctuation, remove it!
  aspellError.chop(1);

  QString message = "kisalib: " + aspellError;
  message += "\nkisalib: Check your Aspell installation!";

  qFatal("%s", message.toLocal8Bit().constData());
}

bool AspellLib::updateDictionary(const int& index) {
  // check the index
  if(index < 0 || index >= m_dictionaries.size()) {
    qDebug() << OBJECT_DEBUG_NAME << index << "is not a valid dictionary index";
    return false;
  }

  // use the actual language code and not our dictionary name
  QString dictionaryCode = m_dictionaryCodes.at(index);

  qDebug() << OBJECT_DEBUG_NAME << "using dictionary language code" << dictionaryCode;
  qDebug() << OBJECT_DEBUG_NAME << "using dictionary" << m_dictionaries.at(index);

  // update the spell checker
  AspellConfig* aspell_config = new_aspell_config();
  aspell_config_replace(aspell_config, "lang", dictionaryCode.toLocal8Bit());
  aspell_config_replace(aspell_config, "encoding", "utf-8");

  AspellCanHaveError* aspell_possible_err = new_aspell_speller(aspell_config);

  // check for errors, if none create the spell checker
  if(aspell_error_number(aspell_possible_err) != 0)
    printErrorAndExit(aspell_error_message(aspell_possible_err));

  else
    m_aspellSpeller = to_aspell_speller(aspell_possible_err);

  // no need for the config any more
  delete_aspell_config(aspell_config);

  // keep track of the index
  m_currentDictionaryIndex = index;

  // hack to overcome first word always misspelled, see issue 1 and issue 5
  // TODO: remove when not needed
  aspell_speller_check(m_aspellSpeller, "xxxxxxx", -1);

  return true;
}

bool AspellLib::updateDictionary(const QString& newLanguage) {
  // try the full language_COUNTRY format first
  int index = m_dictionaryCodes.indexOf(QRegExp(newLanguage,
                                                Qt::CaseInsensitive));

  if(index == -1)
    // try just the language
    index = m_dictionaryCodes.indexOf(QRegExp(newLanguage.left(2),
                                              Qt::CaseInsensitive));

  // don't bother checking the index here, it's done in the other method
  return updateDictionary(index);
}
